package me.lubinn.Vicincantatio.Spells;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import me.lubinn.Vicincantatio.Vicincantatio;

import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerChatEvent;
import org.bukkit.util.Vector;

abstract class ConstructionSpell extends Spell 
{
	protected Area area = null; //area for the spell to effect
	protected Material forceMaterial = null; //force spell to use this material if not null
	protected boolean alwaysBubble = false; //force spell to augment with bubble; used for Bulla
	protected boolean alwaysForce = false; //same with force augment
	protected boolean alwaysFlight = false; //ditto
	protected boolean beforeTarget = false; //whether to center the spell on the target (false) or just before it (true)
	
	static final Map<String, ConstructionSpell> spells = new HashMap<String, ConstructionSpell>();
	static
	{
		spells.put("cubus", new Cubus());
		spells.put("demis", new Demis());
		spells.put("libyes", new Libyes());
		spells.put("sphaera", new Sphaera());
		spells.put("tholus", new Tholus());
		spells.put("turris", new Turris());
		spells.put("discus", new Discus());
		spells.put("annulus", new Annulus());
		spells.put("bulla", new Bulla());
		spells.put("gradi", new Gradi());
		spells.put("restituo", new Restituo());
		spells.put("lux", new Lux());
		spells.put("accendit", new Accendit());
		spells.put("respiro", new Respiro());
		spells.put("frigidus", new Frigidus());
		spells.put("concalesco", new Concalesco());
		spells.put("uanescere", new Uanescere());
		spells.put("evanescere", new Evanescere());
	}
	
	public Spell CastSpell(PlayerChatEvent event, Material mat, int size, Map<String, Boolean> augments)
	{
		if (forceMaterial != null)
			mat = forceMaterial;
		if (mat == null)
			return null;
		
    	Vicincantatio.log.info("In CastSpell");
		boolean force = augments.containsKey("force") || alwaysForce;
		boolean bubble = augments.containsKey("bubble") || alwaysBubble;
		boolean flight = augments.containsKey("flight") || alwaysFlight; 

		Player player = event.getPlayer();
	    World world = player.getWorld();
		Location location = player.getLocation();

	    //get the position of the target
		if (targeted)
		{
		    int range = 500;
			if (flight)
		    	range = 10 + 5*size;
			if (beforeTarget)
			{
		        List<Block> blocks = player.getLastTwoTargetBlocks(null, range);
		        location = blocks.get(0).getLocation(); //get the first of the last two; the one before the last
			}
			else
			{
				location = player.getTargetBlock(null, range).getLocation();
			}
			
		}
		
		//original target coordinates
	    int xorg = location.getBlockX();
	    int yorg = location.getBlockY();
	    int zorg = location.getBlockZ();
	    
	    //boundaries
	    int xlo = area.boundXLow(size);
	    int xhi = area.boundXHi(size);
	    int ylo = area.boundYLow(size);
	    int yhi = area.boundYHi(size);
	    int zlo = area.boundZLow(size);
	    int zhi = area.boundZHi(size);
	    
//	    int nBlocks = 0;
	    
    	for (int x = xorg + xlo; x <= xorg + xhi; x++)
    	{
    		int dx = x - xorg;
	    	for (int z = zorg + zlo; z <= zorg + zhi; z++)
	    	{
	    		int dz = z - zorg;
	    		if (area.InY(size, dx, dz))
	    		{
		    	    for (int y = Math.max(yorg + ylo, 1); y <= Math.min(yorg + yhi, 127); y++)
		    	    {
		    	    	int dy = y - yorg;
//			        	Vicincantatio.log.info("Considering ("+String.valueOf(x)+", "+String.valueOf(y)+
//			        						", "+String.valueOf(z)+")");
			    		boolean inArea;
			    		if (bubble)
			    			inArea = area.OnBorder(size, dx, dy, dz);
			    		else
			    			inArea = area.In(size, dx, dy, dz);
			    		if (inArea)
			    		{
			    			Block block = world.getBlockAt(x, y, z);
			    			ReplaceBlock(block, mat, force);
			    		}
			    	}
	    		}
	    	}
	    }
	    
//	    Vicincantatio.log.info("\tReplacined "+String.valueOf(nBlocks)+" block");
	    
	    Vector V = MoveVector(size);
	    if (augments.containsKey("motion") && V != null)
	    	event.getPlayer().teleport(location.add(V.toLocation(world)));
		return this;
	}
	
	protected void ReplaceBlock(Block block, Material mat, boolean force)
	{
		Material type = block.getType();
		if (type == Material.AIR || (force && type != Material.BEDROCK))
			block.setType(mat);
	}
}

class Cubus extends ConstructionSpell
{
	public Cubus()
	{
		area = new Cube();
	}
	protected Vector MoveVector(int size)
	{
		return new Vector(0, area.Size(size), 0);
	}
}
class Demis extends ConstructionSpell
{
	public Demis()
	{
		area = new LowerHemisphere(4, 3, 0.5);
	}
}
class Sphaera extends ConstructionSpell
{
	public Sphaera()
	{
		area = new HollowSphere();
		targeted = false;
	}
	protected Vector MoveVector(int size)
	{
		return new Vector(0, area.Size(size), 0);
	}
}
class Tholus extends Sphaera
{
	public Tholus()
	{
		area = new UpperHemisphere();
	}
}
class Bulla extends Sphaera
{
	public Bulla()
	{
		alwaysBubble = true;
	}
}
class Libyes extends Sphaera
{
	public Libyes()
	{
		area = new Sphere(1.0, 1.5);
		targeted = true;
	}
}
class Turris extends ConstructionSpell
{
	public Turris()
	{
		area = new Cylinder(2.5, 3.75, 5, 7.5);
	}
	protected Vector MoveVector(int size)
	{
		return new Vector(0, area.Size(size)+1, 0);
	}
}
class Discus extends ConstructionSpell
{
	public Discus()
	{
		area = new Disk();
	}
}
class Annulus extends ConstructionSpell
{
	public Annulus()
	{
		area = new Ring();
		alwaysBubble = true;
	}
	protected Vector MoveVector(int size)
	{
		return null;
	}
}
class Gradi extends ConstructionSpell
{
	public Gradi()
	{
		area = new Platform();
		rangeBase = 15;
		rangeGrowth = 7.5;
		alwaysFlight = true;
	}
}
class Restituo extends ConstructionSpell
{
	public Restituo()
	{
		area = new Point();
		alwaysForce = true;
	}
}
class Lux extends ConstructionSpell
{
	public Lux()
	{
		area = new Point();
		beforeTarget = true;
		forceMaterial = Material.GLOWSTONE;
	}
}
class Accendit extends ConstructionSpell
{
	public Accendit()
	{
		area = new Sphere(1, 2.5);
		forceMaterial = Material.FIRE;
	}
}
class Respiro extends Restituo
{
	public Respiro()
	{
		rangeBase = 0;
		rangeGrowth = 0;
		alwaysFlight = true;
		forceMaterial = Material.AIR;
	}
	protected Vector MoveVector(int size)
	{
		return null;
	}
}

class Frigidus extends ConstructionSpell
{
	public Frigidus()
	{
		area = new Sphere();
		forceMaterial = Material.SNOW;
	}
	public void ReplaceBlock(Block block, Material mat, boolean force)
	{
		Material thisType = block.getType();
		Material botType = block.getRelative(0, -1, 0).getType();
		if (thisType == Material.WATER || thisType == Material.STATIONARY_WATER)
			block.setType(Material.ICE);
		else if (thisType == Material.AIR && botType != Material.AIR && botType != Material.SNOW)
			block.setType(Material.SNOW);
		else if (thisType == Material.LAVA || thisType == Material.STATIONARY_LAVA)
			block.setType(Material.OBSIDIAN);
	}
}
class Concalesco extends ConstructionSpell
{
	public Concalesco()
	{
		area = new Sphere();
		forceMaterial = Material.WATER;
	}
	public void ReplaceBlock(Block block, Material mat, boolean force)
	{
		Material type = block.getType();
		if (type == Material.ICE || type == Material.SNOW_BLOCK)
			block.setType(Material.WATER);
		else if (type == Material.SNOW)
			block.setType(Material.AIR);
		else if (type == Material.OBSIDIAN)
			block.setType(Material.LAVA);
	}
}

class Uanescere extends ConstructionSpell
{
	public Uanescere()
	{
		area = new Sphere();
	}
	public void ReplaceBlock(Block block, Material mat, boolean force)
	{
		if (block.getType() == mat)
			block.setType(Material.AIR);
		if (mat == Material.STATIONARY_WATER || mat == Material.WATER)
			if (block.getType() == Material.WATER || block.getType() == Material.STATIONARY_WATER)
				block.setType(Material.AIR);
		if (mat == Material.LAVA || mat == Material.STATIONARY_LAVA)
			if (block.getType() == Material.LAVA || block.getType() == Material.STATIONARY_LAVA)
				block.setType(Material.AIR);
				
	}
}
class Evanescere extends Uanescere
{
	public Evanescere()
	{
		targeted = false;
	}
	public void ReplaceBlock(Block block, Material mat, boolean force)
	{
		if (block.getType() == mat)
			block.setType(Material.AIR);
		if (mat == Material.STATIONARY_WATER || mat == Material.WATER)
			if (block.getType() == Material.WATER || block.getType() == Material.STATIONARY_WATER)
				block.setType(Material.AIR);
		if (mat == Material.LAVA || mat == Material.STATIONARY_LAVA)
			if (block.getType() == Material.LAVA || block.getType() == Material.STATIONARY_LAVA)
				block.setType(Material.AIR);
	}
}

class Tetra extends ConstructionSpell
{
	public Tetra()
	{
		
	}
}